import pytest
import random
import string
import os
import requests
import boto3
from fixtures import get_order, get_product, iam_auth # pylint: disable=import-error,no-name-in-module
from helpers import get_parameter # pylint: disable=import-error,no-name-in-module


@pytest.fixture(scope="module")
def products_table_name():
    """
    Products DynamoDB table name
    """

    return get_parameter("/ecommerce/{Environment}/products/table/name")


@pytest.fixture(scope="module")
def orders_table_name():
    """
    Orders DynamoDB table name
    """

    return get_parameter("/ecommerce/{Environment}/orders/table/name")


@pytest.fixture(scope="module")
def user_pool_id():
    """
    Cognito User Pool ID
    """

    return get_parameter("/ecommerce/{Environment}/users/user-pool/id")


@pytest.fixture(scope="module")
def delivery_pricing_api_url():
    """
    Delivery Pricing API
    """

    return get_parameter("/ecommerce/{Environment}/delivery-pricing/api/url")


@pytest.fixture
def api_id():
    """
    Frontend GraphQL API ID
    """

    return get_parameter("/ecommerce/{Environment}/frontend-api/api/id")


@pytest.fixture
def api_url():
    """
    Frontend GraphQL API URL
    """

    return get_parameter("/ecommerce/{Environment}/frontend-api/api/url")


@pytest.fixture
def payment_3p_api_url():
    return get_parameter("/ecommerce/{Environment}/payment-3p/api/url")


@pytest.fixture
def api_key(api_id):
    """
    API Key for AppSync
    """

    appsync = boto3.client("appsync")
    response = appsync.create_api_key(apiId=api_id)

    yield response["apiKey"]["id"]

    appsync.delete_api_key(apiId=api_id, id=response["apiKey"]["id"])


@pytest.fixture(scope="module")
def password():
    """
    Generate a unique password for the user
    """

    return "".join(
        random.choices(string.ascii_uppercase, k=10) +
        random.choices(string.ascii_lowercase, k=10) +
        random.choices(string.digits, k=5) +
        random.choices(string.punctuation, k=3)
    )


@pytest.fixture(scope="module")
def email():
    """
    Generate a unique email address for the user
    """

    return "".join(random.choices(string.ascii_lowercase, k=20))+"@example.local"


@pytest.fixture(scope="module")
def client_id(user_pool_id):
    """
    Return a user pool client
    """

    cognito = boto3.client("cognito-idp")

    # Create a Cognito User Pool Client
    response = cognito.create_user_pool_client(
        UserPoolId=user_pool_id,
        ClientName="ecommerce-{}-frontend-api-test".format(os.environ["ECOM_ENVIRONMENT"]),
        GenerateSecret=False,
        ExplicitAuthFlows=["ADMIN_NO_SRP_AUTH"]
    )

    # Return the client ID
    client_id = response["UserPoolClient"]["ClientId"]
    yield client_id

    # Delete the client
    cognito.delete_user_pool_client(
        UserPoolId=user_pool_id,
        ClientId=client_id
    )


@pytest.fixture(scope="module")
def user_id(user_pool_id, email, password):
    """
    User ID generated by Cognito
    """

    cognito = boto3.client("cognito-idp")

    # Create a Cognito user
    response = cognito.admin_create_user(
        UserPoolId=user_pool_id,
        Username=email,
        UserAttributes=[{
            "Name": "email",
            "Value": email
        }],
        MessageAction="SUPPRESS"
    )
    user_id = response["User"]["Username"]
    cognito.admin_set_user_password(
        UserPoolId=user_pool_id,
        Username=user_id,
        Password=password,
        Permanent=True
    )

    # Return the user ID
    yield user_id

    # Delete the user
    cognito.admin_delete_user(
        UserPoolId=user_pool_id,
        Username=user_id
    )


@pytest.fixture(scope="module")
def jwt_token(user_pool_id, user_id, client_id, email, password):
    """
    Returns a JWT token for API Gateway
    """

    cognito = boto3.client("cognito-idp")

    response = cognito.admin_initiate_auth(
        UserPoolId=user_pool_id,
        ClientId=client_id,
        AuthFlow="ADMIN_NO_SRP_AUTH",
        AuthParameters={
            "USERNAME": email,
            "PASSWORD": password
        }
    )

    return response["AuthenticationResult"]["IdToken"]


@pytest.fixture(scope="function")
def product(get_product, products_table_name):
    """
    Product
    """
    table = boto3.resource("dynamodb").Table(products_table_name) # pylint: disable=no-member
    product = get_product()

    table.put_item(Item=product)

    yield product

    table.delete_item(Key={"productId": product["productId"]})


@pytest.fixture(scope="function")
def order_request(get_order, product, iam_auth, delivery_pricing_api_url, payment_3p_api_url):
    """
    Order Request
    """

    order = get_order()

    # Grab the correct delivery price from the backend
    res = requests.post(
        "{}/backend/pricing".format(delivery_pricing_api_url),
        auth=iam_auth(delivery_pricing_api_url),
        json={
            "products": [product],
            "address": order["address"]
        }
    )
    delivery_price = res.json()["pricing"]

    # Get a payment token
    total = delivery_price + sum([p["price"]*p.get("quantity", 1) for p in order["products"]])
    res = requests.post(
        "{}/preauth".format(payment_3p_api_url),
        json={
            "cardNumber": "1234567890123456",
            "amount": total
        }
    )
    payment_token = res.json()["paymentToken"]

    return {
        "products": [product],
        "address": order["address"],
        "deliveryPrice": delivery_price,
        "paymentToken": payment_token
    }


def test_create_order(jwt_token, api_url, order_request):
    """
    Test createOrder
    """

    headers = {"Authorization": jwt_token}

    query = """
    mutation ($order: CreateOrderRequest!) {
      createOrder(order: $order) {
        success
        message
        errors
        order {
            orderId
            userId
        }
      }
    }
    """
    variables = {
        "order": order_request
    }

    response = requests.post(api_url, json={"query": query, "variables": variables}, headers=headers)

    data = response.json()

    print(data)

    assert "data" in data
    assert data["data"] is not None
    assert "createOrder" in data["data"]

    result = data["data"]["createOrder"]
    assert result["success"] == True
    assert "order" in result


def test_get_products(api_url, product, api_key):
    """
    Test getProducts
    """

    headers = {"X-Api-Key": api_key}

    query = """
    query {
      getProducts {
        products {
          productId
          name
        }
      }
    }
    """

    response = requests.post(api_url, json={"query": query}, headers=headers)

    data = response.json()

    assert "data" in data
    assert data["data"] is not None
    assert "getProducts" in data["data"]
    assert "products" in data["data"]["getProducts"]

    found = False
    for res_product in data["data"]["getProducts"]["products"]:
        if res_product["productId"] == product["productId"]:
            found = True
    assert found == True


def test_get_product(api_url, product, api_key):
    """
    Test getProduct
    """

    headers = {"X-Api-Key": api_key}

    query = """
    query ($productId: ID!) {
      getProduct(productId: $productId) {
        productId
        name
      }
    }
    """
    variables = {
        "productId": product["productId"]
    }

    response = requests.post(api_url, json={"query": query, "variables": variables}, headers=headers)

    data = response.json()

    assert "data" in data
    assert data["data"] is not None
    assert "getProduct" in data["data"]

    assert data["data"]["getProduct"]["productId"] == product["productId"]
    assert data["data"]["getProduct"]["name"] == product["name"]


def test_get_orders(jwt_token, api_url):
    """
    Test getOrders
    """

    headers = {"Authorization": jwt_token}

    query = """
    query {
      getOrders {
        orders {
          orderId
          userId
        }
      }
    }
    """

    response = requests.post(api_url, json={"query": query}, headers=headers)

    data = response.json()

    print(data)

    assert "data" in data
    assert "getOrders" in data["data"]
    assert "orders" in data["data"]["getOrders"]


def test_get_delivery_pricing(get_order, jwt_token, api_url):
    """
    Test getDeliveryPricing
    """

    order = get_order()

    headers = {"Authorization": jwt_token}

    query = """
    query($input: DeliveryPricingInput!) {
      getDeliveryPricing(input: $input) {
        pricing
      }
    }
    """
    variables = {
        "input": {
            "products": order["products"],
            "address": order["address"]
        }
    }

    print("VARIABLES", variables)

    response = requests.post(api_url, json={"query": query, "variables": variables}, headers=headers)

    data = response.json()

    print(data)

    assert "data" in data
    assert "getDeliveryPricing" in data["data"]
    assert "pricing" in data["data"]["getDeliveryPricing"]